{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"../common/rfsoc_book_banner.jpg\" alt=\"University of Strathclyde\" align=\"left\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block\" style=\"background-color: #c7b8d6; padding: 10px\">\n",
    "    <p style=\"color: #222222\">\n",
    "        <b>Note:</b>\n",
    "        <br>\n",
    "        This Jupyter notebook uses hardware features of the Zynq UltraScale+ RFSoC device. Therefore, the notebook cells will only execute successfully on an RFSoC platform.\n",
    "        <br>\n",
    "        <b>This Jupyter notebook is not compatible with the ZCU216 development board as it does not contain the SD-FEC integrated block.</b>\n",
    "    </p>\n",
    "</div>\n",
    "\n",
    "# Notebook Set H\n",
    "\n",
    "---\n",
    "\n",
    "## 04 - FEC Decoding\n",
    "This is the fourth notebook in the series of five, exploring Soft Decision Forward Error Correction (SD-FEC) on RFSoC. In previous notebooks we have encoded a block of information bits using an integrated SD-FEC block in the RFSoC's programmable logic. The encoded block was then baseband modulated into symbols which resulted in a complex signal. Noise was added to this signal which was then baseband demodulated into soft bits using soft decisions. In this notebook we will supply these soft bits to an SD-FEC integrated block, configured as a decoder, to decode our data and obtain the original information bits.\n",
    "\n",
    "## Table of Contents\n",
    "* [1. Introduction](#nb4_introduction)\n",
    "    * [1.2. Design Overview](#nb4_design_overview)\n",
    "    * [1.2. Notebook Setup](#nb4_notebook_setup)\n",
    "* [2. Format LLR Values](#nb4_format_llr)\n",
    "* [3. Configure Decoder](#nb4_configure_decoder)\n",
    "* [4. Setup Data Buffers](#nb4_setup_data_buffers)\n",
    "    * [4.1. Control](#nb4_control)\n",
    "    * [4.2. Status](#nb4_status)\n",
    "    * [4.3. Transmit and Receive](#nb4_transmit_and_receive)\n",
    "* [5. Decode Data](#nb4_decode_data)\n",
    "    * [5.1. Bit Error Rate](#nb4_ber)\n",
    "* [6. Conclusion](#nb4_conclusion)\n",
    "\n",
    "## References\n",
    "* [1] - [AMD-Xilinx, \"Soft-Decision FEC Integrated Block v1.1: LogiCORE IP Product Guide\", October 2022](https://docs.xilinx.com/r/en-US/pg256-sdfec-integrated-block)\n",
    "\n",
    "## Revision\n",
    "* **v1.0** | 18/01/23 | *First Revision*\n",
    "* **v1.1** | 19/05/23 | *Minor changes for new development boards (ZCU208 & ZCU216)*\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Introduction <a class=\"anchor\" id=\"nb4_introduction\"></a>\n",
    "This notebook makes use of an SD-FEC block, configured as a decoder in Non-5G New Radio (NR) mode, to decode an encoded data block using the Data-Over-Cable Service Interface Specifications (DOCSIS) standard. The DOCSIS standard supported by the SD-FEC core provides five LDPC codes. The decoder accepts soft bits in the form of Log Likelihood Ratios (LLRs). The formatting of these soft bits into the correct fixed-point value will be demonstrated. After decoding, a Bit Error Rate (BER) calculation will be performed to assess the performance of the LDPC code used. \n",
    "\n",
    "### 1.1. Design Overview <a class=\"anchor\" id=\"nb4_design_overview\"></a>\n",
    "The programmable logic design used in this notebook is illustrated in Figure 1.\n",
    "\n",
    "<a class=\"anchor\" id=\"fig-1\"></a>\n",
    "<center><figure>\n",
    "<img src='./images/sdfec_decoder_block_design.svg' width='1000'/>\n",
    "    <figcaption><b>Figure 1: Functional block diagram illustrating the loop-back implementation of the SD-FEC decoder.</b></figcaption>\n",
    "</figure></center>\n",
    "\n",
    "An SD-FEC block is configured as an LDPC decoder and is in a loop-back architecture with the processing system. Four buffers are required for the movement of data between our Jupyter environment and the SD-FEC core. Two DMAs (ctrl and data) are employed to move data between the Processing System (PS) and Programmable Logic (PL). The ctrl DMA is responsible for the control and status buffers and the data DMA is responsible for the transmit and receive buffers. The transmit buffer will contain formatted LLR values or soft bits. This buffer will be sent to the SD-FEC block where the data will be decoded. The decoded block will be received in the receive buffer and we can compare this to the original information bits that were generated in notebook 2.\n",
    "\n",
    "### 1.2. Notebook Setup <a class=\"anchor\" id=\"nb4_notebook_setup\"></a>\n",
    "We must first setup the notebook by importing the required libraries and downloading the bitstream to the board. We will also import the soft bits (*llrs*) from notebook 3 for decoding and the original information bits (*tx_enc_buf*) from notebook 2 for comparison."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pynq import allocate\n",
    "import numpy as np\n",
    "import xsdfec\n",
    "import strath_sdfec.helper_functions as hf\n",
    "\n",
    "from strath_sdfec.overlay import SdfecOverlay\n",
    "ol = SdfecOverlay()\n",
    "\n",
    "%store -r llrs tx_enc_buf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Format LLR Values <a class=\"anchor\" id=\"nb4_format_llr\"></a> \n",
    "As mentioned, the LLR values must be formatted correctly before being input to the decoder. The SD-FEC core requires that the LLR values are signed 6-bit wordlengths with 2 fractional bits. This data should also be symmetrically saturated. Where regular saturation of this data type would result in the lower and upper bounds [-8, 7.75], symmetric saturation produces the lower and upper bounds [-7.75,7.75]. \n",
    "\n",
    "For the data to be sent using AXI4-Stream, it must be sign-extended to the nearest byte. That means, the sign bit or most significant bit (MSB) of our 6-bit number should be copied and appended to the MSB side of our number twice. Figure 2 illustrates this formatting.\n",
    "\n",
    "<a class=\"anchor\" id=\"fig-2\"></a>\n",
    "<center><figure>\n",
    "<img src='./images/decoder_input.svg' width='350'/>\n",
    "    <figcaption><b>Figure 2: LLR data format for LDPC decoding.</b></figcaption>\n",
    "</figure></center>\n",
    "\n",
    "The process for formatting the LLR data correctly can be broken down into three stages: \n",
    "1. Perform symmetric saturation. \n",
    "2. Convert to a 6-bit 2's complement.\n",
    "3. Sign extend to an 8-bit number.\n",
    "\n",
    "A function is provided below that performs this formatting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def format_llr(llr):\n",
    "    # 1. Perform symmetric saturation\n",
    "    if llr > 7.75:\n",
    "        llr = 7.75\n",
    "    elif llr < -7.75:\n",
    "        llr = -7.75\n",
    "\n",
    "    # Represent as integer\n",
    "    llr = llr * pow(2,2)       # Bit shift to expose 2 fractional values\n",
    "    llr = int(np.round(llr))   # Truncate remaining fractional values\n",
    "\n",
    "    # 2. Convert to binary (2's complement)\n",
    "    if llr >= 0:\n",
    "        binary = '{0:06b}'.format(llr)\n",
    "    else:\n",
    "        binary = '{0:06b}'.format(pow(2,6)+llr)\n",
    "\n",
    "    # 3. Sign extend to 8-bits\n",
    "    if binary[0] == '0': \n",
    "        binary_extended = '00' + binary\n",
    "    else:\n",
    "        binary_extended = '11' + binary\n",
    "\n",
    "    return int(binary_extended,2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using this function, we can loop through our array of LLR values and format each. The LLR values before (blue) and after (red) formatting are plotted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "llrs_formatted = []\n",
    "for llr in llrs:\n",
    "    llrs_formatted.append(format_llr(llr))\n",
    "    \n",
    "hf.plot_samples('LLR Values Before and After Formatting',\n",
    "                [range(80),range(80)],\n",
    "                [llrs[0:80],hf.interpret_llrs(llrs_formatted)[0:80]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To further highlight how the LLR values are formatted, the cell below uses a helper function to print out the original LLR values and their formatted counterparts in both decimal and binary form."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start = 0\n",
    "n = 10\n",
    "hf.print_llr_format_table(llrs,start,n)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our LLR values are now formatted correctly for the SD-FEC core configured as a decoder to interpret.\n",
    "\n",
    "## 3. Configure Decoder <a class=\"anchor\" id=\"nb4_configure_decoder\"></a>\n",
    "Configuring the decoder at run-time is the same as configuring the encoder; simply add the desired code parameters to the SD-FEC core. In the cell below, all the available code parameters are added using the helper function *add_all_ldpc_params()*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fec = ol.ldpc_decoder.sd_fec\n",
    "hf.add_all_ldpc_params(fec)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Setup Buffers <a class=\"anchor\" id=\"nb4_setup_data_buffers\"></a>\n",
    "As with the encoder, the same four buffers are required in our decoder configuration. A transmit buffer will be utilised to send encoded soft bits to the decoder and a receive buffer will move the decoded block back into the PS for inspection. The other two buffers are used for control and status words which must be sent to and received from the decoder for every data block that is to be decoded. Figure 2 below illustrates the movement of data to and from the SD-FEC core when configured as a decoder.\n",
    "\n",
    "<a class=\"anchor\" id=\"fig-2\"></a>\n",
    "<center><figure>\n",
    "<img src='./images/ctrl_status_dec.svg' width='800'/>\n",
    "    <figcaption><b>Figure 2: SD-FEC core interfaces when decoding data.</b></figcaption>\n",
    "</figure></center>\n",
    "\n",
    "### 4.1. Control <a class=\"anchor\" id=\"nb4_control\"></a>\n",
    "When the SD-FEC is configured as a decoder, more fields are available for the control interface. These are detailed in table 1 below.\n",
    "\n",
    "<a class=\"anchor\" id=\"tab-1\"></a>\n",
    "<center><figure>\n",
    "    <figcaption><b>Table 1: Control interface for LDPC decoding in Non-5G NR mode.</b></figcaption>\n",
    "    <br>\n",
    "    <table style=\"width:1000\">\n",
    "      <tr>\n",
    "        <th>Field</th>\n",
    "        <th>Bits</th>\n",
    "        <th>Type</th>\n",
    "        <th>Range</th>\n",
    "        <th>Description</th>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>External Block ID</td>\n",
    "        <td>31:24</td>\n",
    "        <td>uint8</td>\n",
    "        <td>0 to 255</td>\n",
    "        <td>External block identifier to be passed through to status output</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Max No. Iterations</td>\n",
    "        <td>23:18</td>\n",
    "        <td>uint6</td>\n",
    "        <td>1 to 63</td>\n",
    "        <td>Maximum number of iterations</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Terminate on no Change</td>\n",
    "        <td>17:17</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Do not terminate early if there is no change in hard bits for the whole block (information and parity) between iterations<br>\n",
    "            1: Terminate early if there is no change in hard bits for the whole block (information and parity) between iterations</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Terminate on Pass</td>\n",
    "        <td>16:16</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Do not terminate early on passing parity check<br>\n",
    "            1: Terminate early on passing parity check</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Include Parity Output</td>\n",
    "        <td>15:15</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Output systematic values only<br>\n",
    "            1: Output systematic values and parity</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Hard Output</td>\n",
    "        <td>14:14</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Soft output<br>\n",
    "            1: Hard output</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>-</td>\n",
    "        <td>13:7</td>\n",
    "        <td>-</td>\n",
    "        <td>-</td>\n",
    "        <td>Reserved</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Code ID</td>\n",
    "        <td>6:0</td>\n",
    "        <td>uint7</td>\n",
    "        <td>0 to 127</td>\n",
    "        <td> Code number used to specify which set of LDPC code parameters are to be used on the block</td>\n",
    "      </tr>\n",
    "    </table>\n",
    "</figure></center>\n",
    "\n",
    "*External Block ID* and *Code ID* were introduced in notebook 2. The *Max No. Iterations* field specifies the maximum number of iterations the decoder performs. Here, a smaller value can reduce latency but might mean that errors have not been corrected as further iterations were required. The reason this is the maximum number is because the following two fields *Terminate on No Change* and *Terminate on Pass* allow the decoder to terminate early if either of these conditions has been met. The *Hard Output* field can be toggled so that the decoder either outputs hard bits or soft bits.\n",
    "\n",
    "In the code cell below, choose an LDPC code to use when decoding. It is important this matches the code used when encoding. The available codes and their names can be seen in the table output in section 3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Select Code\n",
    "code_name = 'docsis_medium'\n",
    "ldpc_params = fec.available_ldpc_params()\n",
    "code_id = ldpc_params.index(code_name)\n",
    "\n",
    "# Create control word\n",
    "ctrl_params = {'id' : 1, \n",
    "               'max_iterations' : 63,\n",
    "               'term_on_no_change' : 1,\n",
    "               'term_on_pass' : 1, \n",
    "               'include_parity_op' : 0,\n",
    "               'hard_op' : 1,\n",
    "               'code_id' : code_id}\n",
    "ctrl_word = hf.create_ctrl_word(ctrl_params,readout='decoder')\n",
    "\n",
    "# Create control buffer and populate it with control word\n",
    "ctrl_buffer = allocate(shape=(1,), dtype=np.uint32)\n",
    "ctrl_buffer[0] = ctrl_word"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2. Status <a class=\"anchor\" id=\"nb4_status\"></a>\n",
    "The status interface also returns more fields when configured as a decoder. These are detailed in Table 2.\n",
    "\n",
    "<a class=\"anchor\" id=\"tab-2\"></a>\n",
    "<center><figure>\n",
    "    <figcaption><b>Table 2: Status interface for LDPC decoding in Non-5G NR mode.</b></figcaption>\n",
    "    <br>\n",
    "    <table style=\"width:1000\">\n",
    "      <tr>\n",
    "        <th>Field</th>\n",
    "        <th>Bits</th>\n",
    "        <th>Type</th>\n",
    "        <th>Range</th>\n",
    "        <th>Description</th>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>External Block ID</td>\n",
    "        <td>31:24</td>\n",
    "        <td>uint8</td>\n",
    "        <td>0 to 255</td>\n",
    "        <td>External block identifier supplied through control input</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Decode Iterations</td>\n",
    "        <td>23:18</td>\n",
    "        <td>uint6</td>\n",
    "        <td>1 to 63</td>\n",
    "        <td>Number of iterations taken to decode output (either successfully or unsuccessfully)</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Terminate on no Change</td>\n",
    "        <td>17:17</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Did not terminate early due to no change in hard bits for the whole block (information and parity) between iterations<br>\n",
    "            1: Terminated early as no change in hard bits for the whole block (information and parity) between iterations</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Terminate on Pass</td>\n",
    "        <td>16:16</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Did not terminate due to passing parity check<br>\n",
    "            1: Terminated early due to passing parity check</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Parity Pass</td>\n",
    "        <td>15:15</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Parity check did not pass <br>\n",
    "            1: Parity check passed</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Hard Output</td>\n",
    "        <td>14:14</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0 or 1</td>\n",
    "        <td>0: Soft output<br>\n",
    "            1: Hard output</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Decode Operation</td>\n",
    "        <td>13:13</td>\n",
    "        <td>bit1</td>\n",
    "        <td>0</td>\n",
    "        <td>Decode operation (fixed value)</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>-</td>\n",
    "        <td>12:7</td>\n",
    "        <td>-</td>\n",
    "        <td>-</td>\n",
    "        <td>Reserved</td>\n",
    "      </tr>\n",
    "      <tr style=\"text-align:center\">\n",
    "        <td>Code ID</td>\n",
    "        <td>6:0</td>\n",
    "        <td>uint7</td>\n",
    "        <td>0 to 127</td>\n",
    "        <td>Code number specifying the LDPC code parameters used to decode the block</td>\n",
    "      </tr>\n",
    "    </table>\n",
    "</figure></center>\n",
    "\n",
    "Again, only the empty buffer is required to be created at this stage. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "status_buffer = allocate(shape=(1,), dtype=np.uint32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3. Transmit and Receive <a class=\"anchor\" id=\"nb4_transmit_and_receive\"></a>\n",
    "Next, we can create our data buffers. We require one buffer for moving our soft bits into the decoder and another for collecting the decoded hard bits. Let us inspect the parameters for our current code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = fec._code_params.ldpc[code_name]['n']\n",
    "k = fec._code_params.ldpc[code_name]['k']\n",
    "p = fec._code_params.ldpc[code_name]['p']\n",
    "\n",
    "print('Block Length (bits): %s\\nInformation Bits: %s\\nSub-Matrix Size: %s' % (n, k, p))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As established in section 2 of this notebook, each soft bit requires 8 bits to represent it. For an encoded block, we have *n* soft bits with a wordlength of 8. Our AXI4-Stream data width is also 8 bits, meaning that the length of our transmit buffer should equal *n*, the encoded block size. We have configured the output of the decoder to return hard bits and not to include parity bits, thus the decoder will only output the *k* information bits. Again the AXI4-Stream data width is 8 bits, so our receive buffer length must be *k*/8.\n",
    "\n",
    "In the code cell below, we create our two data buffers of the correct size and populate the transmit buffer with the *n* formatted LLR values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "K = int(np.ceil(k/8))\n",
    "\n",
    "tx_dec_buf = allocate(shape=(n,), dtype=np.uint8)\n",
    "rx_dec_buf = allocate(shape=(K,), dtype=np.uint8)\n",
    "\n",
    "j = 0\n",
    "for i in range(len(tx_dec_buf)):\n",
    "    tx_dec_buf[i] = llrs_formatted[j]\n",
    "    j += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Decode Data  <a class=\"anchor\" id=\"nb4_decode_data\"></a>\n",
    "Having configured the SD-FEC core, formatted our LLR values, and created all of the required buffers, we can now perform LDPC decoding. Running the cell below will send our control word and transmit buffer to the SD-FEC block where the LLR values will be decoded. The decoded data output will be returned in our receive buffer along with a status word in the status buffer. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Assign data and ctrl DMAs to shorter variables\n",
    "dma_data = ol.ldpc_decoder.axi_dma_data\n",
    "dma_ctrl = ol.ldpc_decoder.axi_dma_ctrl\n",
    "\n",
    "# Initiate transfers\n",
    "dma_ctrl.recvchannel.transfer(status_buffer)\n",
    "dma_data.recvchannel.transfer(rx_dec_buf)\n",
    "dma_ctrl.sendchannel.transfer(ctrl_buffer)\n",
    "dma_data.sendchannel.transfer(tx_dec_buf)\n",
    "\n",
    "# Wait for transfers to complete\n",
    "dma_ctrl.sendchannel.wait()\n",
    "dma_data.sendchannel.wait()\n",
    "dma_data.recvchannel.wait()\n",
    "dma_ctrl.recvchannel.wait()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Interpreting and printing the received status word, we can insect the returned fields. Note that if a high SNR was used in the previous notebook, you might see that one or both of the conditions have been met to achieve early termination. After completing this notebook, try varying the SNR level in the previous notebook and completing the steps between there and here again to see how the SNR level impacts on the number of decode iterations performed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hf.print_status_reg(status_buffer, 'decoder')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us now plot the first 50 bits of the decoded data (blue) and original data generated in notebook 2 (red). Depending on the SNR employed in the previous notebook, you may or may not see differences in the values obtained. You should expect the data to match, mostly, if using SNRs greater than 12 dB. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tx_bits = hf.serialise(tx_enc_buf)\n",
    "rx_bits = hf.serialise(rx_dec_buf)\n",
    "\n",
    "hf.plot_samples('Tx and Rx',\n",
    "                [range(50),range(50)],\n",
    "                [rx_bits[0:50],tx_bits[0:50]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.1. Bit Error Rate <a class=\"anchor\" id=\"nb4_ber\"></a>\n",
    "\n",
    "While we can inspect the arrays visually to see if there are errors, it is useful to explore other metrics of assessing the performance of the forward error correction. One such metric is Bit Error Rate or BER. This is a ratio of the number of error bits received over the total number of bits transmitted.\n",
    "\n",
    "$$\n",
    "    \\mathit{BER} = \\frac{N_{error}}{N_{bits}}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N_error = np.sum(rx_bits != tx_bits)\n",
    "N_bits = len(tx_bits)\n",
    "BER = N_error / N_bits\n",
    "print('Error Bits', N_error)\n",
    "print('BER:', BER)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While obtaining the BER for one SNR can be useful, it is usually more beneficial to plot a *BER Curve*. This is where BER values are obtained while varying the SNR of the channel and plotting these on a graph. This is a better visualisation of code performance and allows for different codes to be compared more easily. In the next notebook we will combine everything we have touched on thus far to generate BER plots for DOCSIS Short, DOCSIS Medium and DOCSIS Long LDPC codes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Conclusion <a class=\"anchor\" id=\"nb4_conclusion\"></a>\n",
    "This fourth notebook of the series has delved deeper into the details of decoding encoded data using the SD-FEC block on RFSoC. We have learned how to correctly format Log Likelihood Ratios (LLRs) to be input to the decoder, and how to create data buffers of the appropriate size, taking into account the wordlength of the soft LLRs. We also examined the various fields available on the control and status interfaces when the SD-FEC core is operating as a decoder. The decoded data was compared against the original data generated in the second notebook and a performance measure, Bit Error Rate (BER), was introduced. With this knowledge in hand, in the next notebook, we will bring together all the concepts covered in the previous notebooks to generate a series of BER curves for comparing different LDPC codes, which will be a useful tool for assessing and comparing code performance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "[⬅️ Previous Notebook](03_fec_channel_simulation.ipynb) || [Next Notebook 🚀](05_fec_bit_error_analysis.ipynb)\n",
    "\n",
    "Copyright © 2023 Strathclyde Academic Media\n",
    "\n",
    "---\n",
    "---"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
